package org.datacoder.geocoding.datastructures.points

import com.vividsolutions.jts.geom.*
import org.datacoder.geocoding.*
import org.datacoder.geocoding.datastructures.geoshapes.*
import org.datacoder.geocoding.jurisdiction.*

class CellsSubStructure {

    private static final int AVERAGE_NUM_JURISDICTIONS = 20
    private static final GeoPoint[] ZERO_LENGTH_ARRAY = new GeoPoint[0]
    private GeoPoint[][][] cells
    private double lon1, lon2, lonStep
    private double lat1, lat2, latStep
    private int dimensions

    public CellsSubStructure(Collection<Jurisdiction> jurisdictions, Collection<Jurisdiction> envelopeCollection){
        dimensions = (Math.sqrt((1.0*jurisdictions.size())/AVERAGE_NUM_JURISDICTIONS))

        // Determine dimensions of grid
        Polygon[] polygons = new Polygon[envelopeCollection.size()]
        Iterator envelopeCollectionIterator = envelopeCollection.iterator()
        for(int i = 0; i < polygons.length; i++){
            Jurisdiction juris = envelopeCollectionIterator.next()
            polygons[i] = (Polygon)(juris.toGeoRectangle().toGeometry())
        }
        GeometryCollection envelopeMaker = new GeometryCollection(polygons, GeocodingUtils.geometryFactory)
        Envelope envelope = envelopeMaker.getEnvelopeInternal()
        lon1 = envelope.getMinX()
        lon2 = envelope.getMaxX() + 0.01
        lat1 = envelope.getMinY()
        lat2 = envelope.getMaxY() + 0.01
        lonStep = (lon2 - lon1)/dimensions
        latStep = (lat2 - lat1)/dimensions

        // Create geoPoints
        Set<GeoPoint> geoPoints = new HashSet<GeoPoint>()
        for(juris in jurisdictions){
            geoPoints.add(juris.toGeoPoint())
        }

        // Create cells
        List<GeoPoint>[][] tempCells = new List<GeoPoint>[dimensions][dimensions]
        for(gp in geoPoints){
            addToTempCells(gp, tempCells)
        }
        
        cells = new GeoPoint[dimensions][dimensions][]
        for(int i = 0; i < dimensions; i++){
            for(int j = 0; j < dimensions; j++){
                if(tempCells[i][j] == null){
                    cells[i][j] = ZERO_LENGTH_ARRAY
                }else{
                    cells[i][j] = tempCells[i][j].toArray(ZERO_LENGTH_ARRAY)
                }
            }
        }
    }

    public void addToTempCells(GeoPoint gp, List<GeoPoint>[][] tempCells){
        int lonLoc = (gp.lon - lon1)/lonStep
        int latLoc = (gp.lat - lat1)/latStep
        if(tempCells[lonLoc][latLoc] == null){
            tempCells[lonLoc][latLoc] = new LinkedList<GeoPoint>()
        }
        tempCells[lonLoc][latLoc].add(gp)
    }

    public Set<Jurisdiction> findPointsWithinRectangle(GeoRectangle gr){
        Set<Jurisdiction> jurisdictions = new HashSet<Jurisdiction>()
        int minLonLoc = (gr.lon1 - lon1)/lonStep
        int maxLonLoc = (gr.lon2 - lon1)/lonStep
        int minLatLoc = (gr.lat1 - lat1)/latStep
        int maxLatLoc = (gr.lat2 - lat1)/latStep
        if(minLonLoc < 0){
            minLonLoc = 0
        }
        if(maxLonLoc >= dimensions){
            maxLonLoc = dimensions - 1
        }
        if(minLatLoc < 0){
            minLatLoc = 0
        }
        if(maxLatLoc >= dimensions){
            maxLatLoc = dimensions - 1
        }
        for(int i = minLonLoc; i <= maxLonLoc; i++){
            for(int j = minLatLoc; j <= maxLatLoc; j++){
                int length = cells[i][j].length
                for(int k = 0; k < length; k++){
                    if(gr.containsGeoPoint(cells[i][j][k])){
                        jurisdictions.add(cells[i][j][k].juris)
                    }
                }
            }
        }
        return jurisdictions
    }


}

